/*
 * Runs Emitting Thread, Receive Thread and SvgClient Thread.
 * Handles sync between created threads.
 */
package client;

import common.Helper;
import common.IProcessManager;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import serveursvg.IMessageController.MessageType;
/**
 *
 *
 */
public class Parent implements Runnable {

    private Buffer bufferSVG;
    private Buffer bufferReceive;
    private Buffer bufferEmit;
    private ReceiveThread receiveThread;
    private EmittingThread emittingThread;
    private SvgClientThread svgClientThread;
    private Integer clock;
    private Integer processPosition;
    private boolean hasElected = false;

    public Parent() {
        this.bufferSVG = new Buffer();      // tampon de trace
        this.bufferReceive = new Buffer();  // tampon de reception
        this.bufferEmit = new Buffer();     // tampon d'emission
        this.clock = 0;
        this.processPosition = null;

        createAndRunThreads();
        generateLifeLine();
    }

    /**
     * Creates and run the 3 threads:
     *  - receiving thread
     *  - emitting thread
     *  - svg client thread
     */
    private void createAndRunThreads()
    {
        try
        {
            receiveThread = new ReceiveThread(bufferReceive);
            svgClientThread = new SvgClientThread(bufferSVG);
        } catch (RemoteException ex)
        {
            Logger.getLogger(
                    Parent.class.getName()).log(Level.SEVERE, null, ex);
        }
        emittingThread = new EmittingThread(bufferEmit);

        new Thread(receiveThread).start();
        new Thread(emittingThread).start();
        new Thread(svgClientThread).start();
    }


    /**
     * Creation de la ligne de vie du process dans le cache du serveur svg
     */
    private void generateLifeLine()
    {
        ArrayList<String> params = new ArrayList();
        params.add(Helper.getProcessId().toString());
        bufferSVG.put(new BufferMessage(MessageType.PRO, params, clock));
    }

    /**
     * Milestone 12
     * @return if a process as won the election
     */
    private boolean election(Integer valeur)
    {
        boolean won = false;
        BufferMessage message = null;
        ArrayList<String> params = new ArrayList<String>();

        // FIXME: Laurent, this is getting overridden, is it normal?
        message = bufferReceive.get();
        bufferSVG.put(message);
        params.add("setter dans l'emitting thread");
        params.add("setter dans l'emitting thread");
        if(clock<Integer.parseInt(message.getParams().get(3)))
            clock = Integer.parseInt(message.getParams().get(3));
        params.add(clock.toString());
        clock++;
        params.add(clock.toString());
        if(message.getParams().get(5).equals("I win"))
        {
            params.add(message.getParams().get(4));
            params.add(message.getParams().get(5));
            won = true;
        }
        else
        {
            /* si l'ID_machine du message correspond à son ID, cela signifie
             * qu'elle a reçu le message qu'elle a envoyé (elle a donc gagné
             * l'élection et diffuse le message « ID_machine a gagné »
             */
            if (Integer.parseInt(message.getParams().get(4)) == Helper.getProcessId())
            {
                params.add(message.getParams().get(4));
                params.add("I win");
                won = true;
            }
            else
            {
                /* la valeur contenue dans le message est strictement
                 * supérieure à la valeur de la machine, la machine se
                 * désiste et retransmet à son voisin de droite le message
                 * qu'elle a reçu
                 */
                if (Integer.parseInt(message.getParams().get(5)) > valeur)
                {
                    params.add(message.getParams().get(4));
                    params.add(message.getParams().get(5));
                }
                else
                {
                    /* la valeur contenue dans le message est inférieure ou
                    égale à la valeur de la machine, la machine transmet
                    alors à son voisin de droite son propre message (avec
                    son ID_machine et sa valeur)
                     */
                    params.add(Helper.getProcessId().toString());
                    params.add(valeur.toString());
                }
            }
        }
        message = new BufferMessage(MessageType.REAL, params, clock);
        bufferEmit.put(message);

        return won;
    }

    /**
     * Milestone 11
     */
    private void pingPong()
    {
        checkInputBoxAndTransmit();
    }

    private Integer handleFirstElection()
    {

        Random randomGenerator = new Random();
        Integer valeur = 0;
        valeur = randomGenerator.nextInt(1000);
        ArrayList<String> params = new ArrayList<String>();
        // checks if it's the first (registered) process
        processPosition = getProcessPosition();
        // if it's the first it will initiate the message for the ping pong
        if(processPosition == 0)
        {

            valeur = randomGenerator.nextInt(1000);
            hasElected = true;
            params.add("pid source setter dans emitting thread");
            params.add("pid dest setter dans emittting thread");
            params.add("1");
            params.add("2");
            params.add(Helper.getProcessId().toString());
            params.add(valeur.toString());
            BufferMessage message = new BufferMessage(MessageType.REAL, params, clock);
            bufferEmit.put(message);
        }

        return valeur;
    }
    /**
     * If the process is the first one then send the first ping message
     */
    private void handleFirstMessage()
    {
        // checks if it's the first (registered) process
        processPosition = getProcessPosition();
        // if it's the first it will initiate the message for the ping pong
        if(processPosition == 0)
        {
            sendFirstPingPongMessage();
        }
    }

    /**
     * Only the first process should send this first ping pong message
     */
    private void sendFirstPingPongMessage()
    {
        ArrayList<String> params = new ArrayList<String>();
        params.add((Helper.getProcessId().toString()));
        params.add("First message");
        params.add("0");
        params.add("1");
        BufferMessage message = new BufferMessage(
                MessageType.REQ, params, clock);
        bufferEmit.put(message);
    }

    /**
     * Checks receiving buffer for "ping" and transmits it back.
     * Also sends a trace message to the SVG buffer
     */
    private void checkInputBoxAndTransmit()
    {
        BufferMessage message = bufferReceive.get();
        message = transformMessagePingPong(message);
        // Sends the trace message to the svg server
        bufferSVG.put(message);
        bufferEmit.put(message);
    }

    private BufferMessage transformMessagePingPong(BufferMessage message){
        ArrayList<String> params = new ArrayList<String>();

        //premier param : pid source
        params.add((Helper.getProcessId().toString()));
        //second param : pid dest : pid de celui qui a envoyé le message
        params.add(message.getParams().get(0));
        if(clock<Integer.parseInt(message.getParams().get(3)))
            clock = Integer.parseInt(message.getParams().get(3));
        params.add(clock.toString());
        clock++;
        params.add(clock.toString());
        params.add("Mon super contenue de message");
        message.setParams(params);
        return message;
    }


    /**
     * Asks the process manager the current process position.
     * If the current process was the first one registered to the
     * process manager, then its position will be "0".
     * If none process were yet registered the returned postition will be null
     */
    private Integer getProcessPosition()
    {
        Integer tmpProcessPosition = processPosition;
        if(tmpProcessPosition == null)
        {
            Integer processId = Helper.getProcessId();
            ArrayList<Integer> registeredProcesses = null;
            IProcessManager processManager = Helper.getProcessManager();

            try
            {
                registeredProcesses = processManager.getRegisteredProcesses();
            } catch (RemoteException ex)
            {
                Logger.getLogger(Parent.class.getName()).log(Level.SEVERE, null, ex);
            }

            if(!registeredProcesses.isEmpty())
            {
                tmpProcessPosition = registeredProcesses.indexOf(processId);
            }
        }

        return tmpProcessPosition;
    }

    public void run()
    {
        boolean won = false;

        // handleFirstMessage();
        Integer valeur = handleFirstElection();
        while(!won) // loop until a process win the election
        {
            won = election(valeur);
        }
        System.out.println("WIN!");
    }
}